package com.song.druid.config.mybatis;


import com.google.common.base.Predicate;
import com.song.druid.annotation.AutoId;
import com.song.druid.annotation.SensitiveData;
import com.song.druid.util.RandomUtil;
import com.song.druid.util.SensitiveUtil;
import com.song.druid.util.SnowFlakeUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.reflections.ReflectionUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * mybatis自定义ID拦截插件（改造增加了新增和修改数据时对敏感数据加密）
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {
                MappedStatement.class, Object.class
        })
})
@Component
public class AutoIdInterceptor implements Interceptor {

    /**
     *  key值为class对象 value可以理解成是该类带有AutoId注解的属性，只不过对属性封装了一层。
     * 它是非常能够提高性能的处理器 它的作用就是不用每一次一个对象进来都要看下它的哪些属性带有AutoId注解
     * 毕竟类的反射在性能上并不友好。只要key包含该对象那就不需要检查它哪些属性带AutoId注解。
     */
    private Map<Class, List<Handler>> handlerMap = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        //args对应的是注解@Signature中args的参数类型
        MappedStatement mappedStatement = (MappedStatement) args[0];
        //实体对象
        Object entity = args[1];
        if ("INSERT".equalsIgnoreCase(mappedStatement.getSqlCommandType().name())) {
            encrypt(entity);
            Set<Object> entitySet = getEntitySet(entity);
            //批量设置ID
            for (Object object : entitySet) {
                process(object);
            }
        }else if ("UPDATE".equalsIgnoreCase(mappedStatement.getSqlCommandType().name())){
            Map<String, Object> map = (Map) entity;
            Object data = map.get("et");
            encrypt(data);
        }
        return invocation.proceed();
    }

    /**
     * 对敏感数据进行加密
     * @param data
     * @throws IllegalAccessException
     */
    public void encrypt(Object data) throws IllegalAccessException {
        if (data != null){
            Class<?> parameterObjectClass = data.getClass();
            SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
            if (ObjectUtils.isNotEmpty(sensitiveData)){
                Field[] fields = parameterObjectClass.getDeclaredFields();
                SensitiveUtil.encrypt(fields, data);
            }
        }
    }

    /**
     * 给带有AutoId注解的字段赋值
     * @param object 实体对象
     * @throws Throwable
     */
    private void process(Object object) throws Throwable {
        Class<?> handleKey = object.getClass();
        List<Handler> handlers = handlerMap.get(handleKey);

        //性能优化点，如果有两个都是user对象同时,那么只需有个进行反射处理属性就好了,另一个只需执行下面的for循环
        SYNC:
        if (handlers == null) {
            synchronized (this) {
                handlers = handlerMap.get(handleKey);
                if (handlers != null){
                    break SYNC;
                }
                handlerMap.put(handleKey, handlers = new ArrayList<>());
                //反射工具，获取所有带AutoId属性的字段
                Set<Field> fields = ReflectionUtils.getAllFields(object.getClass(), (Predicate<Field>) input -> input != null && input.getAnnotation(AutoId.class) != null);
                for (Field field : fields) {
                    AutoId autoId = field.getAnnotation(AutoId.class);
                    if (field.getType().isAssignableFrom(String.class)){
                        if (autoId.value().equals(AutoId.IdType.UUID)){
                            handlers.add(new UUIDHandler(field));
                        }else if (autoId.value().equals(AutoId.IdType.SNOWFLAKE)){
                            handlers.add(new UniqueStringHandler(field));
                        }
                    }else if (field.getType().isAssignableFrom(Long.class)){
                        if (autoId.value().equals(AutoId.IdType.TIMESTAMP_RANDOM)){
                            handlers.add(new TimestampRandomHandler(field));
                        }else if (autoId.value().equals(AutoId.IdType.SNOWFLAKE)){
                            handlers.add(new UniqueLongHandler(field));
                        }
                    }
                }

            }
        }
        for (Handler handler : handlers) {
            handler.accept(object);
        }
    }

    /**
     * object是需要插入数据库的实体数据，可以是对象，也可以是批量插入的对象
     * 如果是对象，那么object就是当前对象
     * 如果是批量插入对象，那么object就是一个Map集合，key为list，value为List集合
     * @param object
     * @return
     */
    private Set<Object> getEntitySet(Object object) {
        Set<Object> set = new HashSet<>();
        if (object instanceof Map) {
            //批量插入
            Collection values = (Collection) ((Map) object).get("list");
            for (Object value : values) {
                if (value instanceof Collection) {
                    set.addAll((Collection) value);
                }else {
                    set.add(value);
                }
            }
        } else {
            set.add(object);
        }
        return set;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private static abstract class Handler {
        Field field;

        Handler (Field field){
             this.field = field;
        }

        abstract void handle(Field field, Object object) throws Throwable;

        private boolean checkField(Object object, Field field) throws IllegalAccessException {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            //如果注解对应的属性已经赋值，那么不再通过插件生成ID
            return field.get(object) == null;
        }

        public void accept(Object o) throws Throwable {
            if (checkField(o, field)) {
                handle(field, o);
            }
        }
    }

    /**
     * UUID
     */
    private static class UUIDHandler extends Handler {
        UUIDHandler(Field field){
            super(field);
        }

        @Override
        void handle(Field field, Object object) throws Throwable {
            field.set(object, UUID.randomUUID().toString().replaceAll("-", ""));
        }
    }

    /**
     * 雪花算法Long型ID
     */
    private static class UniqueLongHandler extends Handler {
        UniqueLongHandler(Field field){
            super(field);
        }

        @Override
        void handle(Field field, Object object) throws Throwable {
            field.set(object, SnowFlakeUtil.generateId());
        }
    }

    /**
     * 雪花算法String型ID
     */
    private static class UniqueStringHandler extends Handler {
        UniqueStringHandler(Field field){
            super(field);
        }

        @Override
        void handle(Field field, Object object) throws Throwable {
            field.set(object, SnowFlakeUtil.generateStringId());
        }
    }

    /**
     * 时间戳随机生成ID
     */
    private static class TimestampRandomHandler extends Handler {
        TimestampRandomHandler(Field field){
            super(field);
        }

        @Override
        void handle(Field field, Object object) throws Throwable {
            field.set(object, RandomUtil.timestampRandom());
        }
    }
}
